package com.zhou.base.components.bs.common.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.zhou.base.components.bs.common.exception.ErrorController;
import com.zhou.base.components.bs.common.exception.GlobalExceptionHandler;
import com.zhou.base.components.bs.common.properties.CommonProperties;
import com.zhou.base.components.bs.common.util.MessageUtils;
import com.zhou.base.components.bs.common.util.SpringUtils;
import com.zhou.base.components.bs.common.util.id.SnowflakeUtil;
import com.zhou.base.components.bs.common.util.id.TransNoUtil;
import feign.FeignException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.DispatcherServlet;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;

/**
 * @author xianfeng
 * @EnableConfigurationProperties({CommonProperties.class, ServerProperties.class, WebMvcProperties.class})
 * EnableConfigurationPropertie注入的properties不能立刻注入当前类，@Autowired 智能通过定义bean的方法注入
 * @date 2023/7/25 20:21
 */
@Import({XxlJobConfig.class})
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
@EnableConfigurationProperties({CommonProperties.class, ServerProperties.class, WebMvcProperties.class})
@Configuration
public class CommonConfig implements CommandLineRunner {


    @Bean
    public SpringUtils springUtils() {
        return new SpringUtils();
    }

    @Override
    public void run(String... args) throws Exception {
        ListableBeanFactory beanFactory = SpringUtils.getBeanFactory();
        if (beanFactory != null) {
            MessageUtils.messageSource = beanFactory.getBean(MessageSource.class);
        }
    }

    @ConditionalOnBean(DispatcherServlet.class)
    @Configuration
    static class SpringWebConfig {

        @Bean
        @ConditionalOnMissingBean(value = org.springframework.boot.web.servlet.error.ErrorController.class, search = SearchStrategy.CURRENT)
        public ErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                    ObjectProvider<ErrorViewResolver> errorViewResolvers, ServerProperties serverProperties) {
            return new ErrorController(errorAttributes,
                    errorViewResolvers.orderedStream().collect(Collectors.toList()), serverProperties.getError());
        }
    }

    @ConditionalOnClass({RedisTemplate.class, Jackson2ObjectMapperBuilder.class})
    @Configuration
    static class DependenceRedisConfig {
        @Bean
        @ConditionalOnMissingBean
        public TransNoUtil transNoUtil() {
            return new TransNoUtil();
        }

        @Bean("redisTemplate")
        @ConditionalOnMissingBean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory,
                                                           Jackson2ObjectMapperBuilder builder) {
            ObjectMapper objectMapper = builder.createXmlMapper(false).build();
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

            RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
            // fastjson序列化
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
            redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
            redisTemplate.setConnectionFactory(connectionFactory);
            return redisTemplate;
        }

        /**
         * 基于mybatis里的雪花算法（使用redis 生成工作id，和数据中心id）
         *
         * @return
         */
        @Bean
        @ConditionalOnMissingBean
        public SnowflakeUtil.RedisSnowflakeWorkerId snowflakeWorkerId() {
            return new SnowflakeUtil.RedisSnowflakeWorkerId();
        }
    }

    @ConditionalOnBean(DispatcherServlet.class)
    @ConditionalOnClass({FeignException.class, MyBatisSystemException.class})
    @Configuration
    static class DependenceWebConfig {
        @Bean
        @ConditionalOnMissingBean
        public GlobalExceptionHandler baseExceptionHandler(CommonProperties CommonProperties) {
            return new GlobalExceptionHandler(CommonProperties);
        }
    }

    @ConditionalOnClass(CorsFilter.class)
    @Configuration
    static class DependenceCorsFilterConfig {
        @Bean
        @ConditionalOnMissingBean
        public CorsFilter corsFilter(CommonProperties CommonProperties) {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowedOriginPatterns(CommonProperties.getCorsAllowedOrigins());
            configuration.addAllowedHeader("*");
            configuration.addAllowedMethod("*");
            configuration.setAllowCredentials(true);
            configuration.addExposedHeader("Authorization");
            configuration.addExposedHeader("UserToken");
            configuration.addExposedHeader("Content-Disposition");
            configuration.setMaxAge(1800L);
            source.registerCorsConfiguration("/**", configuration);
            return new CorsFilter(source);
        }
    }

    @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
    @Configuration
    static class DependenceJacksonConfig {
        @Bean
        @ConditionalOnMissingBean
        public Jackson2ObjectMapperBuilderCustomizer customizer(CommonProperties CommonProperties) {
            return builder -> {
                builder.serializerByType(LocalDateTime.class,
                        new LocalDateTimeSerializer(DateTimeFormatter
                                .ofPattern(CommonProperties.getDateTimePattern())));
                builder.deserializerByType(LocalDateTime.class,
                        new LocalDateTimeDeserializer(DateTimeFormatter
                                .ofPattern(CommonProperties.getDateTimePattern())));
                builder.serializerByType(LocalDate.class,
                        new LocalDateSerializer(DateTimeFormatter.ofPattern(CommonProperties.getDatePatter())));
                builder.deserializerByType(LocalDate.class,
                        new LocalDateDeserializer(DateTimeFormatter.ofPattern(CommonProperties.getDatePatter())));
            };
        }
    }


}
